using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.IO;
using Sio.Mdm.Utils;

namespace Sio.Mdm.Server
{
    /// <summary>
    /// Class that should resolve missing references
    /// </summary>
    public static class Resolver
    {
        #region Fields
        private static Dictionary<string, Assembly> CachedAssemblies = new Dictionary<string,Assembly>();
        #endregion

        #region Construction
        static Resolver()
        {
            AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;            
        }
        #endregion

        #region Properties

        #region Private

        #region Paths
        private static List<string> _paths;
        internal static List<string> Paths
        {
            get
            {
                if (_paths == null)
                {
                    _paths = new List<string>();

                    foreach (ServiceInformation service in Config.Services)
                    {
                        if (service.IsFromDisk)
                        {
                            _paths.Add(Path.GetDirectoryName(service.Address));
                        }
                    }
                }

                return _paths;
            }
        }
        #endregion

        #endregion

        #endregion

        #region Methods

        #region Public

        #region AddProbingPath
        /// <summary>
        /// Add path that is usde for assembly resolving
        /// </summary>
        /// <param name="path"></param>
        public static void AddProbingPath(string path)
        {
            path = Path.GetDirectoryName(path);

            if (!Paths.Contains(path))
            {
                Paths.Add(path);
            }
        }
        #endregion

        #region Initialize
        /// <summary>
        /// Initialize types resolver
        /// </summary>
        public static void Initialize()
        {
        }
        #endregion

        #endregion

        #region Private

        #region LoadAssembly
        private static Assembly LoadAssembly(string assemblyName)
        {
            Assembly result = null;

            try
            {
                string filePath = null;
                string fileName = assemblyName.Substring(0);
                int count = Paths.Count;

                Logger.WriteInformation("Resolving asembly {0}", assemblyName);

                for (int i = 0; i < count; i++)
                {
                    if (assemblyName.Contains(","))
                    {
                        string[] parts = assemblyName.Split(',');
                        filePath = String.Format("{0}\\{1}.dll", Paths[i], parts[0]);
                    }
                    else if (assemblyName.LastIndexOf('.') != -1)
                    {
                        filePath = assemblyName;
                    }
                    else if (Path.IsPathRooted(Paths[i]))
                    {
                        filePath = String.Format("{0}\\{1}\\{2}", AppDomain.CurrentDomain.BaseDirectory, Paths[i], fileName);
                    }
                    else
                    {
                        filePath = String.Format("{0}\\{1}", Paths[i], fileName);
                    }

                    if (File.Exists(filePath))
                    {
                        Logger.WriteInformation("Loading type from {0}", filePath);
                        result = Assembly.LoadFrom(filePath);
                    }

                    if (result != null)
                        return result;
                }
            }
            catch (ReflectionTypeLoadException ex)
            {
                Logger.WriteError("Type load failed");
                Logger.WriteException(ex);                

                foreach (Exception inner in ex.LoaderExceptions)
                {
                    Logger.WriteException(inner);
                }

                result = null;
            }
            catch (Exception ex)
            {
                Logger.WriteError("Type load failed");
                Logger.WriteException(ex);

                result = null;
            }

            return result;
        }
        #endregion

        #endregion

        #endregion

        #region Events

        #region Private

        #region CurrentDomain_AssemblyResolve
        private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            Assembly result = null;

            if (!CachedAssemblies.ContainsKey(args.Name))
            {
                Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();

                // TODO: Cache with "AssemblyLoad" event
                foreach (Assembly asm in assemblies)
                {
                    if (asm.FullName.Contains(args.Name))
                    {
                        result = asm;
                        break;
                    }
                }

                if (result == null)
                {
                    result = LoadAssembly(args.Name);
                }

                CachedAssemblies.Add(args.Name, result);
            }
            else
            {
                result = CachedAssemblies[args.Name];
            }

            return result;
        } 
        #endregion

        #endregion

        #endregion
    }
}
